package com.sdy.auth.client.service;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.sdy.common.model.Token;
import com.sdy.common.utils.Assert;
import com.sdy.common.utils.DateUtil;
import com.sdy.common.utils.RandomUtil;
import com.sdy.common.utils.StringUtil;
import com.sdy.redis.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Service
public class TokenService {
    private static String ISS = "com.sdy.auth";
    private static String TOKEN_KEY = ISS + ".token.key";
    private static int TOKEN_KEY_SECOND = 86400 * 10; // 每隔10天更新一次key
    private static int TOKEN_EXPIRE_SECOND = 60 * 60 * 24; // token过期时间
    
    @Autowired
    private RedisService redisService;

    public Token createToken(String userNo) {
        Token tk = null;
        try {
            Assert.isBlank(userNo, "用户不能为空");
            String key = getKey();
            Date now = new Date();
            Date expDate = DateUtil.addSecond(now, TOKEN_EXPIRE_SECOND);
            // Guest 延长token有效时间
            if (userNo.equals("-1")) {
                expDate = DateUtil.addSecond(now, TOKEN_EXPIRE_SECOND * 100);
            }
            Map<String, Object> map = new HashMap<>();
            map.put("alg", "HS256");
            map.put("typ", "JWY");
            String token = JWT.create()
                    .withHeader(map)
                    .withExpiresAt(expDate)
                    .withIssuedAt(now)
                    .withIssuer(ISS)
                    .withSubject(userNo)
                    .sign((Algorithm.HMAC256(key)));
            tk = new Token();
            tk.setToken(token);
            tk.setSub(userNo);
            tk.setIat(now);
            tk.setExp(expDate);
        } catch (Exception e) {
            log.error("Token创建失败，用户No={}", userNo, e);
        }
        return tk;
    }

    public Token verifyToken(String token) {
        if (StringUtil.isBlank(token)) {
            return null;
        }
        Token tk = null;
        try {
            String key = getKey();
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(key))
                    .withIssuer(ISS)
                    .build();
            DecodedJWT jwt = verifier.verify(token);
            Map<String, Claim> claimMap = jwt.getClaims();
            tk = new Token();
            tk.setToken(token);
            tk.setSub(claimMap.get("sub").asString());
            tk.setIat(claimMap.get("iat").asDate());
            tk.setExp(claimMap.get("exp").asDate());
        } catch (TokenExpiredException e){
            log.info("Token已过期");
        } catch (Exception e){
            log.error("Token校验失败", e);
        }
        return tk;
    }

    public Token reGenToken(Token oldToken) {
        if ((oldToken.getExp().getTime() + oldToken.getIat().getTime()) < System.currentTimeMillis() * 2) {
            return this.createToken(oldToken.getSub());
        }
        return null;
    }

    /**
     * 动态生成密钥
     * 可能造成某个时间用户token失效需要重新登录。可以通过task监控token记录，在一段时间没人使用的时候趁机更换密钥
     * @return
     */
    private String getKey() {
        String key = redisService.get(TOKEN_KEY, String.class);
        if (key == null) {
            key = RandomUtil.produceStringAndNumber(32);
            redisService.set(TOKEN_KEY, key, TOKEN_KEY_SECOND);
        }
        return key;
    }
}
